using System;
using System.Collections;
using Evaluant.Uss.Commands;
using Evaluant.Uss.Common;
using System.Globalization;

namespace Evaluant.Uss.Sync
{
	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// The ids that are returned are those from the default persistence engine. Thus a TraceEngine can't be used 
	/// as a Default one.
	/// </remarks>
	public class SyncEngine : IPersistenceEngine
	{
		private string clientId;

		public string ClientId
		{
			get { return clientId; }
			set { clientId = value; }
		}

		private IPersistenceEngine engine;
		public IPersistenceEngine Engine
		{
			get { return engine; }
			set { engine = value; }
		}

		private IPersistenceEngine metadataEngine;

		public IPersistenceEngine MetadataEngine
		{
			get { return metadataEngine; }
			set { metadataEngine = value; }
		}

		private IPersistenceEngine secondaryMetadataEngine;

		public IPersistenceEngine SecondaryMetadataEngine
		{
			get { return secondaryMetadataEngine; }
			set { secondaryMetadataEngine = value; }
		}

		private bool ignoreClientMetadata;

		public bool IgnoreClientMetadata
		{
			get { return ignoreClientMetadata; }
			set { ignoreClientMetadata = value; }
		}

		private int lastTransactionId;

		public int LastTransactionId
		{
			get { return lastTransactionId; }
		}

		public CultureInfo Culture
		{
			get { return null; }
			set { }
		}

		public SyncEngine(IPersistenceEngine engine, IPersistenceEngine metadataEngine, IPersistenceEngine secondaryMetadataEngine, string clientId)
		{
			this.engine = engine;
			this.metadataEngine = metadataEngine;
			this.secondaryMetadataEngine = secondaryMetadataEngine;
			this.clientId = clientId;
		}

		public EntitySet Load(string opath)
		{
			return Load(opath, new string[0]);
		}

		public EntitySet Load(string opath, string[] attributes)
		{
			return Load(opath, attributes, null, 1, 0);
		}

		public EntitySet Load(string opath, string orderby, int first, int max)
		{
			return Load(opath, new string[0], orderby, first, max);
		}

		public EntitySet LoadWithId(string type, string[] id)
		{
			return LoadWithId(type, id, new string[0]);
		}

		public Entity LoadWithId(string type, string id)
		{
			EntitySet result = LoadWithId(type, new string[] { id });
			return result.Count > 0 ? result[0] : null;
		}

		public void LoadReference(Entity entity)
		{
			LoadReference(new Entity[] { entity });
		}

		public void LoadReference(Entity entity, string[] references)
		{
			LoadReference(new Entity[] { entity }, references);
		}

		public void LoadReference(IEnumerable entities)
		{
			LoadReference(entities, new string[0]);
		}

		/// <summary>
		/// Initializes information about the last transaction number
		/// </summary>
		/// <param name="engine"></param>
		private void InitializeInfo(IPersistenceEngine engine)
		{
			Entity e = new Entity(SyncUtils.INFO);
			e.SetValue(SyncUtils.TRANSACTION, 0);
			e.SetValue(SyncUtils.CLIENTID, String.Empty);

			Transaction t = new Transaction();
			t.Serialize(e);
			t.Commit(engine, false);
		}

		public void InitializeRepository()
		{
			engine.InitializeRepository();
			metadataEngine.InitializeRepository();
			InitializeInfo(metadataEngine);

			if (secondaryMetadataEngine != null)
			{
				secondaryMetadataEngine.InitializeRepository();
				InitializeInfo(secondaryMetadataEngine);
			}
		}

		public void Initialize()
		{
			engine.Initialize();
			metadataEngine.Initialize();

			if (secondaryMetadataEngine != null)
				secondaryMetadataEngine.Initialize();
		}

		public EntitySet Load(OPath.OPathQuery opath, string[] attributes, string orderby, int first, int max)
		{
			return engine.Load(opath, attributes, orderby, first, max);
		}

		public EntitySet Load(string opath, string[] attributes, string orderby, int first, int max)
		{
			return engine.Load(opath, attributes, orderby, first, max);
		}

		public void LoadReference(System.Collections.IEnumerable entities, string[] references)
		{
			engine.LoadReference(entities, references);
		}

		public EntitySet LoadWithId(string type, string[] id, string[] attributes)
		{
			return engine.LoadWithId(type, id, attributes);
		}

		public object LoadScalar(string opath)
		{
			return engine.LoadScalar(opath);
		}

		public object LoadScalar(OPath.OPathQuery opath)
		{
			return engine.LoadScalar(opath);
		}
		public void BeforeProcessCommands(Transaction tx)
		{
			engine.BeforeProcessCommands(tx);
		}

		/// <summary>
		/// Generates a TimeStamp like number representing an ordered transaction number
		/// </summary>
		/// <param name="metadataengine"></param>
		private void GenerateTransactionId(IPersistenceEngine engine)
		{
			Entity info;
			string guid;

			EntitySet es = engine.Load(SyncUtils.INFO);
			if (es.Count == 0)
			{
				InitializeInfo(engine);
				es = engine.Load(SyncUtils.INFO);
			}

			info = es[0];
			guid = Guid.NewGuid().ToString();

			do
			{
				lastTransactionId = info.GetInt32(SyncUtils.TRANSACTION);
				info.SetValue(SyncUtils.TRANSACTION, ++lastTransactionId);
				info.SetValue(SyncUtils.CLIENTID, guid);

				Transaction t = new Transaction();
				t.Serialize(info);
				t.Commit(engine, false);

				es = engine.Load(SyncUtils.INFO);
				if (es.Count == 0)
				{
					InitializeInfo(engine);
					es = engine.Load(SyncUtils.INFO);
				}

				info = es[0];

			} while (info.GetInt32(SyncUtils.TRANSACTION) != lastTransactionId || info.GetString(SyncUtils.CLIENTID) != guid);

		}

		public void ProcessCommands(Transaction tx)
		{
			engine.ProcessCommands(tx);

			Transaction t;
			SyncCommandProcessor syncCommandProcessor;

			GenerateTransactionId(metadataEngine);

			t = new Transaction();
			syncCommandProcessor = new SyncCommandProcessor(t, lastTransactionId);

			foreach (Command c in tx.PendingCommands)
			{
				// Ignores commands in metadata
				if (c.IgnoreMetadata || ignoreClientMetadata)
					continue;

				c.Accept(syncCommandProcessor);
			}

			t.Commit(metadataEngine, false);

			if (secondaryMetadataEngine != null)
			{
				GenerateTransactionId(secondaryMetadataEngine);

				t = new Transaction();
				syncCommandProcessor = new SyncCommandProcessor(t, lastTransactionId);

				foreach (Command c in tx.PendingCommands)
				{
					c.Accept(syncCommandProcessor);
				}

				t.Commit(secondaryMetadataEngine, false);
			}
		}

		public void AfterProcessCommands(Transaction tx)
		{
			engine.AfterProcessCommands(tx);
		}

		public Evaluant.Uss.Models.Model Model
		{
			get { return engine.Model; }
		}
	}
}
